#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/spi.h"

#include "wizchip_conf.h"
#include "w5100s.h"
#include "bsp_spi.h"
#include "socket.h"
#include "loopback.h"
#include "MQTTClient.h"
#include "mqtt_interface.h"
#include "dns.h"
#include "string.h"
#include "dhcp.h"
#include "cJSON.h"

/* define socket */
#define SOCK_DHCP 0
#define SOCK_DNS 0
#define SOCK_MQTT 0

#define MQTT_SEND_BUFF_SIZE 2048         /*Send cache size*/
#define MQTT_RECV_BUFF_SIZE 2048         /*Recv cache size*/
#define ETHERNET_BUF_MAX_SIZE (1024 * 2) // Send and receive cache size

#define DHCP_RETRY_COUNT 5 // DHCP retry times
#define DNS_RET_FAIL 0
#define DNS_RET_SUCCESS 1
#define DNS_RETRY 35 /* 3 times */

#define LED_PIN 25
#define ON 1
#define OFF 0

enum status
{
    CONN = 0,
    SUB,
    PUB,
    KEEPALIVE,
    ERROR = 255,
} run_status;

uint8_t mqtt_send_buff[MQTT_SEND_BUFF_SIZE] = {0};
uint8_t mqtt_recv_buff[MQTT_RECV_BUFF_SIZE] = {0};

typedef struct MQTTCONNECTION
{
    char mqttHostUrl[1024]; /*Server URL*/
    uint8_t server_ip[4];   /*Server IP*/
    int port;               /*Server port number*/
    char clientid[1024];    /*client ID*/
    char username[1024];    /*user name*/
    char passwd[1024];      /*user passwords*/
    char pubtopic[255];     /*publication*/
    char pubtopic_reply[255];
    char subtopic[255];       /*subscription*/
    char subtopic_reply[255]; /*subscription*/
    int pubQoS;               /* publishing messages*/
    int subQoS;               /* subscription messages*/
    char willtopic[255];      /*Will topic  */
    int willQoS;              /*Will message  */
    char willmsg[255];        /*Will */
} mqttconn;

mqttconn mqtt_params = {
    .mqttHostUrl = "mqtts.heclouds.com",
    .server_ip = {
        0,
    },                                                                                                                                                     /*Define the Connection Server IP*/
    .port = 1883,                                                                                                                                          /*Define the connection service port number*/
    .clientid = "W5100S_W5500",                                                                                                                            /*Define the client ID*/
    .username = "70TwP2gxl5",                                                                                                                              /*Define the user name*/
    .passwd = "version=2018-10-31&res=products%2F70TwP2gxl5%2Fdevices%2FW5100S_W5500&et=1791400694&method=sha1&sign=0SchVg6Y2MRYn%2B9zItNZwt%2F%2FN4Y%3D", /*Define user passwords*/
    .pubtopic = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/post",                                                                                        /*Define the publication message*/
    .pubtopic_reply = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/post/reply",
    .subtopic = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/set",
    .subtopic_reply = "$sys/70TwP2gxl5/W5100S_W5500/thing/property/set_reply", /* vDefine subscription messages*/
    .pubQoS = 0,                                                               /*Defines the class of service for publishing messages*/
    .subQoS = 0,                                                               /*Defines the class of service for subscription messages*/
};

MQTTMessage pubmessage = {
    .qos = 0,
    .retained = 0,
    .dup = 0,
    .id = 0,
};
MQTTPacket_willOptions willdata = MQTTPacket_willOptions_initializer; /* Will subject struct initialization */
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;     /*Define the parameters of the MQTT connection*/
unsigned char *data_ptr = NULL;

wiz_NetInfo net_info = {
    .mac = {0x00, 0x09, 0xdc, 0x16, 0xed, 0x2e}, /*Define the W5100S default mac address*/
    .ip = {192, 168, 1, 123},                    /*Define the W5100S default IP*/
    .sn = {255, 255, 255, 0},                    /*Define the W5100S default subnet mask*/
    .gw = {192, 168, 1, 1},                      /*Define the W5100S default gateway*/
    .dns = {8, 8, 8, 8},                         /*Define the W5100S default DNS server*/
    .dhcp = NETINFO_DHCP};                       /*Define the W5100S as statically acquiring IP mode*/
MQTTClient c = {0};
Network n = {0};
wiz_NetInfo get_info;
static uint8_t breakout_flag = 0; // Define the DHCP acquisition flag
int connOK;
int pub_time = 0;

static uint8_t ethernet_buf[ETHERNET_BUF_MAX_SIZE] = {
    0,
}; // Send and receive cachestatic uint8_t destip[4]={192, 168, 1, 2};  // udp destination ip

/**
 * @brief   Initialization of chip network information
 * @param   conf_info :Static configuration information
 * @return  none
 */
void network_init(wiz_NetInfo *conf_info);

/*
    @brief Callback processing after the 1 ms timer is triggered.
    @param Timer struct.
    @return True.
 */
bool repeating_timer_1ms_callback(struct repeating_timer *t);

/*
    @brief Callback processing after the 1 s timer is triggered.
    @param Timer struct.
    @return True.
 */
bool repeating_timer_1s_callback(struct repeating_timer *t);

/*
    @brief MQTT client and connection initialization functions.
    @param None.
    @return None.
 */
void mqtt_init(void);

/*
   @brief Subscribe to the topic callback function.
   @param Message queue.
   @return None.
*/
void messageArrived(MessageData *md);

/*
   @brief The DNS resolves the domain name.
   @param1 Domain name to be resolved.
   @param2 Domain Resolved IP address.
   @return None.
*/
void do_dns(uint8_t *domain_name, uint8_t *remote_ip);

/*
   @brief Analyte model parameters.
   @param Received object model messages.
   @return None.
*/
void json_decode(uint8_t *msg);

int main()
{
    /* Variable definition */
    int ret;
    struct repeating_timer timer_1s;
    struct repeating_timer timer_1ms;
    uint8_t pubmsg[128] = {0};

    /*mcu init*/
    stdio_init_all();     /*Initialize the serial port*/
    wizchip_initialize(); /*Initialize the SPI*/

    /* LED init */
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);

    /*timer init*/
    add_repeating_timer_ms(1000, repeating_timer_1s_callback, NULL, &timer_1s); // Add DHCP and DNS 1s Tick Timer handler
    add_repeating_timer_ms(1, repeating_timer_1ms_callback, NULL, &timer_1ms);  // Add MQTT 1ms Tick Timer handler

    /*dhcp init*/
    DHCP_init(SOCK_DHCP, ethernet_buf); // DHCP initialization

    /*dns init*/
    DNS_init(SOCK_DNS, ethernet_buf);

    /* Set the network address information */
    printf("wiznet chip mqtt of onenet example.\r\n");
    network_init(&net_info);              // Configuring Network Information
    print_network_information(&get_info); // Read back the configuration information and print it

    /* Resolve mqtt broker domain names using dns */
    do_dns(mqtt_params.mqttHostUrl, mqtt_params.server_ip);

    /*mqtt init*/
    mqtt_init();

    while (true)
    {
        switch (run_status)
        {
        case CONN:
        {
            ret = MQTTConnect(&c, &data); /* Connect to the MQTT server */
            printf("Connect to the MQTT server: %d.%d.%d.%d:%d\r\n", mqtt_params.server_ip[0], mqtt_params.server_ip[1], mqtt_params.server_ip[2], mqtt_params.server_ip[3], mqtt_params.port);
            printf("Connected:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
            if (ret != SUCCESSS)
            {
                run_status = ERROR;
            }
            else
            {
                run_status = SUB;
            }
            break;
        }
        case SUB:
        {
            ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.subQoS, messageArrived); /* Subscribe to Topics */
            printf("Subscribing to %s\r\n", mqtt_params.subtopic);
            printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
            if (ret != SUCCESSS)
            {
                run_status = ERROR;
            }
            else
            {
                run_status = KEEPALIVE;
            }
            ret = MQTTSubscribe(&c, mqtt_params.pubtopic_reply, mqtt_params.subQoS, NULL); /* Subscribe to Topics */
            printf("Subscribing to %s\r\n", mqtt_params.pubtopic_reply);
            printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
            if (ret != SUCCESSS)
            {
                run_status = ERROR;
            }
            else
            {
                run_status = KEEPALIVE;
            }
            break;
        }
        case PUB:
        {
            if (pub_time >= 5)
            {
                pub_time = 0;
                sprintf(pubmsg, "{\"id\": \"123\",\"version\": \"1.0\",\"params\": {\"LEDSwitch\": {\"value\":%s}}}", gpio_get(LED_PIN) == ON ? "true" : "false");
                pubmessage.payload = pubmsg;
                pubmessage.payloadlen = strlen(pubmessage.payload);
                ret = MQTTPublish(&c, mqtt_params.pubtopic, &pubmessage); /* Publish message */
                if (ret != SUCCESSS)
                {
                    run_status = ERROR;
                }
                else
                {
                    printf("publish:%s,%s\r\n\r\n", mqtt_params.pubtopic, pubmessage.payload);
                }
            }
            else
            {
                run_status = KEEPALIVE;
            }
            break;
        }
        case KEEPALIVE:
        {
            if (MQTTYield(&c, 30) != SUCCESSS) /* keepalive MQTT */
            {
                run_status = ERROR;
            }
            else
            {
                run_status = PUB;
            }
            sleep_ms(100);
            break;
        }
        case ERROR: /* Running error */
            printf("system ERROR!\r\n");
            sleep_ms(1000);
            break;

        default:
            break;
        }
    }
}

bool repeating_timer_1ms_callback(struct repeating_timer *t)
{
    MilliTimer_Handler();
    return true;
}

bool repeating_timer_1s_callback(struct repeating_timer *t)
{
    pub_time++;
    DHCP_time_handler();
    DNS_time_handler(); // DNS 1s Tick Timer handler
    return true;
}

void mqtt_init(void)
{
    NewNetwork(&n, 1);                                           /*Obtain network configuration information*/
    ConnectNetwork(&n, mqtt_params.server_ip, mqtt_params.port); /*Connect to the MQTT server*/
    MQTTClientInit(&c, &n, 1000, mqtt_send_buff, MQTT_SEND_BUFF_SIZE, mqtt_recv_buff, MQTT_RECV_BUFF_SIZE);
    data.willFlag = 0;                                                            /* will flag */
    data.MQTTVersion = 4;
    data.clientID.cstring = mqtt_params.clientid;
    data.username.cstring = mqtt_params.username;
    data.password.cstring = mqtt_params.passwd;
    data.keepAliveInterval = 30;
    data.cleansession = 1;
}

void messageArrived(MessageData *md)
{
    int ret;
    char topicname[64] = {0};
    char msg[256] = {0};

    sprintf(topicname, "%.*s", (int)md->topicName->lenstring.len, md->topicName->lenstring.data);
    sprintf(msg, "%.*s", (int)md->message->payloadlen, (char *)md->message->payload);
    printf("recv:%s,%s\r\n\r\n", topicname, msg);

    json_decode(msg);
}

void network_init(wiz_NetInfo *conf_info)
{
    int count = 0;
    uint8_t dhcp_retry = 0;

    if (conf_info->dhcp == NETINFO_DHCP)
    {
        while (true)
        {
            switch (DHCP_run()) // Do the DHCP client
            {
            case DHCP_IP_LEASED: // DHCP resolves the domain name successfully
            {
                if (breakout_flag == 0)
                {
                    printf("DHCP success\r\n");
                    getIPfromDHCP((*conf_info).ip);
                    getGWfromDHCP((*conf_info).gw);
                    getSNfromDHCP((*conf_info).sn);
                    getDNSfromDHCP((*conf_info).dns);
                    wizchip_setnetinfo(conf_info); // Configuring Network Information
                    close(SOCK_DHCP);              // After dhcp close the socket, avoid errors in later use
                    breakout_flag = 1;
                }
                break;
            }
            case DHCP_FAILED:
            {
                printf(" DHCP failed \r\n");
                count++;
                if (count <= DHCP_RETRY_COUNT) // If the number of times is less than or equal to the maximum number of times, try again
                {
                    printf("DHCP timeout occurred and retry %d \r\n", count);
                }
                else if (count > DHCP_RETRY_COUNT) // If the number of times is greater than DHCP fails
                {
                    breakout_flag = 1; // if DHCP fail, use the static
                    DHCP_stop();       // Stop processing DHCP protocol
                    conf_info->dhcp = NETINFO_STATIC;
                    wizchip_setnetinfo(conf_info); // Configuring Network Information
                    break;
                }
                break;
            }
            }
            if (breakout_flag)
            {
                printf("config success\r\n");
                break;
            }
        }
    }
    else
    {
        wizchip_setnetinfo(conf_info); // Configuring Network Information
    }
}

void do_dns(uint8_t *domain_name, uint8_t *remote_ip)
{
    uint8_t dns_retry_cnt = 0; // Number of parsings
    uint8_t dns_ok = 0;        // Parse success Peugeot
    wiz_NetInfo read_info = {
        0,
    };
    wizchip_getnetinfo(&read_info);
    while (true)
    {
        if ((dns_ok == 0) && (dns_retry_cnt < DNS_RETRY)) // Determine whether the parsing is successful or whether the parsing exceeds the number of times
        {
            switch (DNS_run(read_info.dns, domain_name, remote_ip)) // Read the DNS_run return value
            {
            case DNS_RET_FAIL: // The DNS domain name is successfully resolved
            {
                sleep_ms(1000);
                dns_retry_cnt++;
                printf("> DNS Failed\r\n");
            }
            break;
            case DNS_RET_SUCCESS: // DNS domain name resolution failed
            {
                dns_ok = 1;
                printf("> Translated %s to %d.%d.%d.%d\r\n", domain_name, remote_ip[0], remote_ip[1], remote_ip[2], remote_ip[3]);
            }
            break;
            }
        }
        else
        {
            break;
        }
    }
}

void json_decode(uint8_t *msg)
{
    int ret;
    uint8_t replymsg[128] = {0};
    cJSON *jsondata = NULL;
    cJSON *params = NULL;
    cJSON *LED = NULL;
    cJSON *ID = NULL;
    jsondata = cJSON_Parse(msg);
    if (jsondata == NULL)
    {
        printf("json parse fail.\r\n");
        return;
    }
    ID = cJSON_GetObjectItem(jsondata, "id");
    params = cJSON_GetObjectItem(jsondata, "params");
    LED = cJSON_GetObjectItem(params, "LEDSwitch");
    if (LED->valueint == 1)
    {
        gpio_put(LED_PIN, ON);
        printf("LED ON!\r\n");
    }
    else
    {
        gpio_put(LED_PIN, OFF);
        printf("LED OFF!\r\n");
    }
    pubmessage.qos = 0;
    sprintf(replymsg, "{\"id\":\"%s\",\"code\":200,\"msg\":\"success\"}", ID->valuestring);
    pubmessage.payload = replymsg;
    pubmessage.payloadlen = strlen(pubmessage.payload);
    ret = MQTTPublish(&c, mqtt_params.subtopic_reply, &pubmessage); /* Publish message */
    if (ret != SUCCESSS)
    {
        run_status = ERROR;
    }
    else
    {
        printf("publish:%s,%s\r\n\r\n", mqtt_params.subtopic_reply, pubmessage.payload);
    }
}
